/******************************************************************************
 * @brief    命令管理器
 * Change Logs:
 * Date           Author         Email                   Notes
 * 2024-09-18      子恒     3393018959@qq.com      Initial version
 ******************************************************************************/
#include "cmd.h"

static b_cmd_mgr_t b_cmd_mgr;
static ring_buf_t cmds_ring = {NULL,0,0,0};
static unsigned char cmd_ring_buf[B_CMD_RING_MAX];

static uint8_t cb_cmd_help(char *argv[],uint8_t argc);
static const b_cmd_t cmds_help = {.cmd_str = "HELP",.cb = cb_cmd_help};

static unsigned char string_split(char *buf, char **argv,unsigned char * argc);
static unsigned char parse_str(char *str,char ch);
static unsigned char char_in_string(char *str, char ch);
static unsigned int str_hash(char *str);
static unsigned char str_match(const char *str1, char **argv, unsigned char argc);

static void cmd_put_char(const char ch);
static void cmd_print_var_cb(char *argv[],uint8_t argc);
    
unsigned char cmd_ring_puts(unsigned char *pch,unsigned int len)
{
    if(ring_buf_put(&cmds_ring, pch, len) == len) return B_CMD_RET_PASS;
    return B_CMD_RET_FAIL;
}

void cmd_mgr_init(b_cmd_t *cmds,unsigned char cmds_len, b_cmd_out_char_cb_t * putc_cb)
{
    unsigned short i,j;
    ring_buf_init(&cmds_ring, cmd_ring_buf, B_CMD_RING_MAX);
    b_cmd_mgr.pf_outchar = putc_cb;
    b_cmd_mgr.cmds = cmds;
    b_cmd_mgr.info.cmds_len = cmds_len;
    memset(b_cmd_mgr.line, 0, sizeof(b_cmd_mgr.line));
    
    for(i = 0; i < b_cmd_mgr.info.cmds_len; i++)
    {
        const char *pstr = b_cmd_mgr.cmds[i].cmd_str;
        unsigned int hash = 0;
        unsigned char argc_mask = 0x80;
        j = 0;
        while(*pstr)
        {
            if(char_in_string(B_CMD_SPLIT_STR,*pstr) != B_CMD_RET_PASS)
            {
                hash += (*pstr) << j;
                j++;
                if(argc_mask & 0x80){
                    argc_mask &= ~0x80;
                    argc_mask++;
                }
            }
            else
            {
                j = 0;
                argc_mask |= 0x80;
            }
            pstr++;
        }
        argc_mask &= 0x7f;
        b_cmd_mgr.cmds[i].hash = hash;
        b_cmd_mgr.cmds[i].hash_argc = argc_mask;
    }
}

void b_cmd_parse(void)
{
    static uint16_t index = 0;
    uint8_t argc = 0,ch,i,j;
    char * argv[B_CMD_ARGS_NUM_MAX + 1] = {0};
    unsigned short hash_map[B_CMD_ARGS_NUM_MAX + 1];
    unsigned char ret = B_CMD_RET_FAIL;
    
    uint8_t len = ring_buf_get(&cmds_ring, &ch, 1);
    if(len != 1) return;
    
    b_cmd_mgr.line[index++] = ch;
    if(index >= B_CMD_LINE_CHAR_MAX)
    {
        index = 0;
        b_cmd_put_str("[Command buffer exceeded!!]\r\n");
    }
    if(parse_str(B_CMD_END_STR,ch) == B_CMD_RET_PASS)
    {
        b_cmd_mgr.line[index - strlen(B_CMD_END_STR)] = '\0';
        index = 0;
        string_split(b_cmd_mgr.line,argv,&argc);
        hash_map[0] = str_hash(argv[0]);
        for(i = 1; i < argc; i++)
        {
            hash_map[i] = hash_map[i-1] + str_hash(argv[i]);
        }
        
        for(i = 0; i < b_cmd_mgr.info.cmds_len; i++){
            unsigned int hash = 0;
            if(b_cmd_mgr.cmds[i].hash == hash_map[b_cmd_mgr.cmds[i].hash_argc - 1])
            {
                ret = str_match(b_cmd_mgr.cmds[i].cmd_str,argv,b_cmd_mgr.cmds[i].hash_argc);
                if(ret == B_CMD_RET_PASS)
                {
                    ret = b_cmd_mgr.cmds[i].cb(argv,argc);
                    break;
                }
            }
        }
        
        if((ret != B_CMD_RET_PASS) && (argc == 1))
        {
            if(!strcmp(cmds_help.cmd_str,argv[0]))
            {
                ret = cmds_help.cb(argv,argc);
            }
        }
        
        if(ret == B_CMD_RET_PASS){
            b_cmd_put_str(B_CMD_EXE_SUCESS_STR);
        }else{
            b_cmd_put_str(B_CMD_EXE_FAIL_STR);
        }
        cmd_print_var_cb(argv,argc);
    }
}

void b_cmd_put_str(const char *const str)
{
    const char *ptemp = str;
    if (b_cmd_mgr.pf_outchar != NULL)
    {
        while (*ptemp)
        {
            b_cmd_mgr.pf_outchar(*ptemp);
            ptemp++;
        }
    }
}

//检测ch是否按字符串str的顺序逐个写入
static unsigned char parse_str(char *str,char ch)
{
    static unsigned int pt = 0;
    if(str[pt] == ch) pt++;
    if(str[pt] == '\0')
    {
        pt = 0;
        return B_CMD_RET_PASS;
    }
    return B_CMD_RET_FAIL;
}

//检测ch是否在字符串str中存在
static unsigned char char_in_string(char *str, char ch)
{
    while (*str)
    {
        if (*str == ch)
            return B_CMD_RET_PASS;
        str++;
    }
    return B_CMD_RET_FAIL;
}

//获取字符串哈希和
static unsigned int str_hash(char *str)
{
    unsigned int hash = 0;
    unsigned char i = 0;
    while(*str)
    {
        hash += (*str) << i;
        i++;
        str++;
    }
    return hash;
}

//从母串中匹配子串，忽略分隔符
static unsigned char str_match(const char *str1, char **argv, unsigned char argc)
{
    unsigned char flg = 0,i = 0;
    const char *p1 = str1;
    char *p2 = argv[i];
    for(i = 0; i < argc; )
    {
        if (char_in_string(B_CMD_SPLIT_STR, *p1) == B_CMD_RET_PASS)
        {
            p1++;
            if(flg == 1)
            {
                flg = 0;
            }
        }
        else
        {
            if(flg == 0)
            {
                flg = 1;
                p2 = argv[i];
                i++;
            }
            while(*p2)
            {
                if(!(*p2 == *p1))
                {
                    return B_CMD_RET_FAIL;
                }
                p1++;
                p2++;
            }
        }
    }
    return B_CMD_RET_PASS;
}

//按B_CMD_SPLIT_STR字符串中字符，将buf分割成子字符串并将其首地址储存在argv[]中，argc为分割的子字符串数量
//忽略首分割字符、连续多个分割字符。
static unsigned char string_split(char *buf, char **argv,unsigned char * argc)
{
    unsigned char i = 0;
    if(argv == NULL) return B_CMD_RET_FAIL;
    do{
        if (char_in_string(B_CMD_SPLIT_STR, buf[i]) != B_CMD_RET_PASS){
            break;
        }
        i++;
    }while(1);
    
    argv[(*argc)++] = &buf[i];
    
    for (; buf[i] != '\0'; i++)
    {
        if (char_in_string(B_CMD_SPLIT_STR, buf[i]) == B_CMD_RET_PASS)
        {
            buf[i] = '\0';
            if (char_in_string(B_CMD_SPLIT_STR, buf[i + 1]) == B_CMD_RET_FAIL)
            {
                if (buf[i + 1] != '\0')
                {
                    argv[(*argc)++] = &buf[i + 1];
                }
            }
        }
        if (*argc > B_CMD_ARGS_NUM_MAX)
        {
            break;
        }
    }
    return B_CMD_RET_PASS;
}

static uint8_t cb_cmd_help(char *argv[],uint8_t argc)
{
    unsigned char i;
    const char *tab="0123456789ABCDEF";
    
    b_cmd_put_str("CMD LIST\r\n");
    b_cmd_put_str("----------------------------------------------------\r\n");
    for(i = 0; i < b_cmd_mgr.info.cmds_len; i++){
        b_cmd_put_str(b_cmd_mgr.cmds[i].cmd_str);
        b_cmd_put_str("\t0x");
        cmd_put_char(tab[(((b_cmd_mgr.cmds[i].hash) & 0xF000) >> 12)]);
        cmd_put_char(tab[(((b_cmd_mgr.cmds[i].hash) & 0x0F00) >> 8)]);
        cmd_put_char(tab[(((b_cmd_mgr.cmds[i].hash) & 0x00F0) >> 4)]);
        cmd_put_char(tab[(((b_cmd_mgr.cmds[i].hash) & 0x000F) >> 0)]);
        b_cmd_put_str("\t");
        if(b_cmd_mgr.cmds[i].cmd_help != NULL)b_cmd_put_str(b_cmd_mgr.cmds[i].cmd_help);
        b_cmd_put_str("\r\n");
    }
    b_cmd_put_str("----------------------------------------------------\r\n");
    return B_CMD_RET_PASS;
}

static void cmd_print_var_cb(char *argv[],uint8_t argc)
{
    #if B_CMD_VAR_PRINT_EN == 1
    uint8_t i = 0;
	const char *tab="0123456789ABCDEF";
    for (i = 0; i < argc; i++)
    {
        b_cmd_put_str("argv[");
        cmd_put_char(tab[((i & 0xf0) >> 4)]);
        cmd_put_char(tab[((i & 0x0f) >> 0)]);
        b_cmd_put_str("] = ");
        b_cmd_put_str(argv[i]);
        b_cmd_put_str("\r\n");
    }
    #endif
}

static void cmd_put_char(const char ch)
{
    if (b_cmd_mgr.pf_outchar != NULL)
    {
        b_cmd_mgr.pf_outchar(ch);
    }
}